package org.dhbw.mosbach.ai.cmd.session;

import org.dhbw.mosbach.ai.cmd.model.User;
import org.dhbw.mosbach.ai.cmd.util.CmdConfig;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.validation.constraints.NotNull;
import javax.ws.rs.core.Context;

/**
 * The session utility is a simplification regarding interactions with the user
 * session by providing mechanisms to create new sessions, check if a requesting
 * user is logged in, getting user information from the session and finally
 * destroy and invalidate the session.
 *
 * For this purpose, this utility uses the CDI injection mechanism to inject
 * an incoming request in form of a {@link HttpServletRequest} into the constructor
 * of this utility annotated with {@code @Inject}. It is then stored inside the
 * request attribute and obtained whenever a service requires access to the session.
 * With the request attribute the utility gains access to the session which is a
 * managed bean stored at server side. The empty default constructor has to be
 * provided so that this utility bean is proxyable by the CDI container.
 *
 * The passed {@link HttpServletRequest} by the container is injected using the
 * context aware injection mechanism provided by the JAX-RS library and may not
 * be {@code null} at runtime. If the request is null, a {@link NullPointerException}
 * is thrown. Also, the returned session of the request may not be {@code null}
 * to reliably enable setting and accessing of session attributes and invalidating
 * the session. However, these objects are guaranteed not to be {@code null} by
 * the container.
 *
 * The session utility can be used by injecting it into the respective bean
 * implementation using the {@code @Inject} annotation. Do not use the public
 * no-args constructor as this is only provided for proxying implementations
 * generated by the injection handler of the container. Do not use the other
 * public constructor with the request parameter either. Instead use it like
 * this:
 *
 * <pre>
 * {@code
 * @RequestScoped
 * class JavaBean {
 *     @Inject
 *     private SessionUtil sessionUtil;
 *
 *     public void useSession() {
 *         if (sessionUtil.isLoggedIn()) {
 *             // Do something with the session
 *         }
 *     }
 * }
 * }
 * </pre>
 *
 * The session utility needs to be {@code @RequestScoped} as well as the the objects
 * injecting this utility to enable instantiation on every new request that is
 * required because the HTTP request is injected each time. Otherwise the request
 * attribute remains the same for subsequent requests which is not desired.
 *
 * @author 6694964
 * @version 1.2
 *
 * @see HttpServletRequest
 * @see HttpSession
 * @see Inject
 */
@RequestScoped
public class SessionUtil {

    /**
     * The client's HTTP request to an API service provided by context sensitive
     * injection
     */
    private HttpServletRequest request;

    /**
     * This constructor is only applied for context sensitive injection by JAX-RS
     * for the class to be proxyable by the container. Do not use this constructor.
     * Instead inject this utility into your bean with the {@code @Inject} annotation
     * like described above.
     */
    public SessionUtil() {
    }

    /**
     * Constructor used for injecting the user HTTP request into the session utility.
     * The context aware injection is automatically done by the proxy implementation
     * the container generates. The request parameter may not be {@code null} because
     * in that case a {@link NullPointerException} is thrown. Therefore this constructor
     * should not be used directly as the injection automatically cares about the correct
     * instantiation. Use this class by injecting this utility into your class as described
     * above.
     *
     * @param request the HTTP servlet request of the user containing a reference to the
     *                user session
     * @see Context
     */
    @Inject
    public SessionUtil(@NotNull @Context HttpServletRequest request) {
        this.request = requireNotNull(request);
    }

    /**
     * Creates a new session for the specified {@code user} if there is no active session
     * for the user yet. This is the case if the user is not authenticated at the
     * application or his original session expired. The new session is associated with
     * the user object of the current user and a boolean value whether the user is logged
     * in or not.
     *
     * @param user the user for which a new session will be created
     * @return true if a new session was created, false if the user already has an
     * active session.
     */
    public boolean createSession(User user) {
        if (isLoggedIn()) {
            return false;
        }

        HttpSession session = request.getSession(true);
        session.setAttribute(CmdConfig.SESSION_USER, user);
        session.setAttribute(CmdConfig.SESSION_IS_LOGGED_IN, true);

        return true;
    }

    /**
     * Retrieves the information whether the current user is logged in or not. This is
     * achieved by checking if an active session is being maintained for the user and
     * if so, whether the session is bind to the user object.
     *
     * @return true if the current user is logged in, false otherwise.
     */
    public boolean isLoggedIn() {
        return request.getSession(false) != null && getUser() != null;
    }

    /**
     * Invalidates the session of the current user if there is an active session being
     * maintained. If the user is authenticated and has an active session the session
     * attributes are removed firstly and the session is destroyed and invalidated
     * secondly.
     *
     * @return true if the session was invalidated, false if the user is not authenticated
     * and thus has no active session.
     */
    public boolean invalidateSession() {
        if (!isLoggedIn()) {
            return false;
        }

        HttpSession session = request.getSession(false);
        session.removeAttribute(CmdConfig.SESSION_USER);
        session.removeAttribute(CmdConfig.SESSION_IS_LOGGED_IN);
        session.invalidate();

        return true;
    }

    /**
     * Retrieves the user object of the current user from the session. It is checked
     * if the user has an active session, and in that case the user object is loaded
     * from this session and returned. The method returns {@code null} if there is no
     * actively maintained session at the server or if the session is not bound to
     * any user object.
     *
     * @return the concrete user object loaded from the session or {@code null} if
     * the user has no active session or the session does not contain a user object.
     */
    public User getUser() {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return null;
        }

        return (User) session.getAttribute(CmdConfig.SESSION_USER);
    }

    /**
     * Checks if the applied HTTP servlet request is {@code null} or not. If the
     * request parameter is null a {@link NullPointerException} is thrown. Otherwise
     * the request is returned as is. This method should be invoked in a case where
     * a non-null parameter is required.
     *
     * @param request the HTTP servlet request to be checked against {@code null}
     * @return the request as is if it is not {@code null}, otherwise throws
     * {@link NullPointerException}.
     * @see java.util.Objects#requireNonNull(Object)
     */
    private HttpServletRequest requireNotNull(HttpServletRequest request) {
        if (request == null) {
            throw new NullPointerException("HTTP request is null");
        }

        return request;
    }
}
